package com.wuliang.blog.core.adapter;


import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.classmate.TypeBindings;
import com.fasterxml.classmate.TypeResolver;
import com.fasterxml.classmate.types.ResolvedObjectType;
import com.wuliang.blog.core.basic.BasicEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.reflections.Reflections;
import org.reflections.scanners.SubTypesScanner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.builders.ModelBuilder;
import springfox.documentation.builders.ModelPropertyBuilder;
import springfox.documentation.schema.Model;
import springfox.documentation.schema.ModelProperty;
import springfox.documentation.schema.ModelProvider;
import springfox.documentation.schema.ResolvedTypes;
import springfox.documentation.schema.TypeNameExtractor;
import springfox.documentation.spi.schema.contexts.ModelContext;
import springfox.documentation.spi.service.contexts.DocumentationContext;
import springfox.documentation.spi.service.contexts.RequestMappingContext;
import springfox.documentation.spring.web.plugins.DocumentationPluginsManager;
import springfox.documentation.spring.web.scanners.ApiModelReader;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

/**
 * 因原有的 Swagger 不支持不在 controller 中的实体类显示，
 * 所以此处将 ApiModel 的载入方法进行重写，以扩展支持不同包中的实体类。
 */
@Primary
@Component
public class EntityReader extends ApiModelReader {

    @Autowired
    TypeNameExtractor typeNameExtractor;

    @Autowired
    public EntityReader(@Qualifier("default") ModelProvider modelProvider,
                        TypeResolver typeResolver,
                        DocumentationPluginsManager pluginsManager) {
        super(modelProvider, typeResolver, pluginsManager);
    }

    /**
     * 对 ApiModel 的载入方式进行重写
     *
     * @param context controller 上下文
     * @return 全部的实体类对象
     */
    @Override
    public Map<String, Model> read(RequestMappingContext context) {
//        先用原有的载入方法将实体类读取一遍
        Map<String, Model> modelList = super.read(context);
//        获取公共包中的实体类对象
        Reflections reflections = new Reflections("com.jxkj.common.entities", new SubTypesScanner(true));
//        声明一个类型解析对象，以 BaseEntity 为根， 创建一个类型解析对象
        ResolvedType resolvedType = ResolvedObjectType.create(BasicEntity.class, TypeBindings.emptyBindings(), null, new ArrayList<>());
//        然后获取 swagger 的上下文对象
        DocumentationContext documentationContext = context.getDocumentationContext();
//        遍历 BaseEntity 子类的实体类对象
        reflections.getSubTypesOf(BasicEntity.class).forEach(aClass -> {
//            声明子类的类型解析对象
            ResolvedType resolvedObjectType = new ResolvedObjectType(aClass, TypeBindings.emptyBindings(), resolvedType, new ArrayList<>());
//            创建 ApiModel 的上下文对象
            ModelContext modelContext = ModelContext.inputParam(documentationContext.getGroupName(),
                    context.getReturnType(),
                    documentationContext.getDocumentationType(),
                    documentationContext.getAlternateTypeProvider(),
                    documentationContext.getGenericsNamingStrategy(),
                    documentationContext.getIgnorableParameterTypes()
            );
//            声明一个 以字段名为键， swagger字段模型为值的 map对象
            Map<String, ModelProperty> mergedTargetProperties = new HashMap<>();
//            遍历当前实体类中的所有私有字段
            for (Field field : aClass.getDeclaredFields()) {
//                获取这些字段上面的注解说明
                ApiModelProperty modelProperty = field.getAnnotation(ApiModelProperty.class);
//                如果注解说明不为空，则继续
                if (modelProperty != null) {
                    mergedTargetProperties.put(
//                            以字段名为键
                            field.getName(),
//                            用字段模型构造器来创建，字段模型
                            new ModelPropertyBuilder()
//                                    获取 ApiModelProperty 注解中的字段说明
                                    .description(modelProperty.value())
//                                    声明类型解析对象，
                                    .type(new ResolvedObjectType(field.getType(), TypeBindings.emptyBindings(), resolvedObjectType, new ArrayList<>()))
//                                    获取 ApiModelProperty 注解中的 name 属性，也可以更改为字段名称[field.getName()]
                                    .name(modelProperty.name())
//                                    构造出一个字段模型
                                    .build()
//                                    updateModelRef 方法原文用义不明，但不声明的话，会导致页面上实体类中的字段不显示
                                    .updateModelRef(ResolvedTypes.modelRefFactory(modelContext, typeNameExtractor))
                    );
                }
            }
//            获取 当前类上的 ApiModel 注解对象
            ApiModel apiModel = aClass.getAnnotation(ApiModel.class);
//            如果 没有标注注解则返回
            if (apiModel == null) {
                return;
            }
//            用对象模型构造器来声明对象模型
            Model model = new ModelBuilder()
                    .id(aClass.getSimpleName())
                    .name(aClass.getSimpleName())
                    .type(resolvedObjectType)
                    .qualifiedType(aClass.getTypeName())
                    .description(apiModel.value())
                    .baseModel(null)
                    .discriminator(null)
                    .subTypes(null)
                    .properties(mergedTargetProperties)
                    .build();
            modelList.put(aClass.getSimpleName(), model);
        });
        return modelList;
    }

}
